﻿/*
 *  Copyright (c) 2012 Brandon Ellenberger
 *
 *  This file is part of OpSatPlan.
 *
 *  OpSatPlan is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  OpSatPlan is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OpSatPlan.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OpSatPlan.PlanningGraph
{
    public class Level
    {
        private Level mPreviousLevel;
        private Dictionary<ValuedAtomicFormula, ValuedAtomicFormula> mStartLits;
        private Dictionary<ValuedAtomicFormula, ValuedAtomicFormula> mEndLits;
        private Dictionary<Action, Action> mSeenActions;
        private List<Action> mActions;
        private List<Operator> mOperators;
        private RelaxedPlanningGraph mGraph;
        private int mNumberActionsAdded;
        internal Level(RelaxedPlanningGraph graph)
        {
            this.mGraph = graph;
            this.mPreviousLevel = null;
            this.mStartLits = new Dictionary<ValuedAtomicFormula, ValuedAtomicFormula>();
            this.mEndLits = new Dictionary<ValuedAtomicFormula, ValuedAtomicFormula>();
            this.mSeenActions = new Dictionary<Action, Action>();
            foreach (ValuedAtomicFormula vaf in this.mGraph.Problem.InitialState)
            {
                this.mEndLits.Add(vaf, vaf);
            }
            this.mActions = new List<Action>();
            this.mOperators = new List<Operator>();
            this.mNumberActionsAdded = -1;
        }
        internal Level(RelaxedPlanningGraph graph, Level previous)
        {
            this.mGraph = graph;
            this.mPreviousLevel = previous;
            this.mStartLits = new Dictionary<ValuedAtomicFormula, ValuedAtomicFormula>(previous.mEndLits);
            this.mEndLits = new Dictionary<ValuedAtomicFormula, ValuedAtomicFormula>(this.mStartLits);
            this.mActions = new List<Action>(previous.mActions);
            this.mSeenActions = new Dictionary<Action, Action>(previous.mSeenActions);
            if (previous.mNumberActionsAdded != 0)
            {
                this.PopulateAndInvokePossibleActions();
            }
            this.mOperators = new List<Operator>();
            foreach (ActionDefinition ad in this.mGraph.Domain.ActionDefinitions)
            {
                Operator op = new Operator(ad);
                var res = from act in this.mActions where act.ActionDefinition == ad select act;
                foreach (Action act in res)
                {
                    op.Add(act);
                }
                this.mOperators.Add(op);
            }
        }
		public bool HasLeveledOff()
		{
			return this.mNumberActionsAdded == 0;
		}
        private void PopulateAndInvokePossibleActions()
        {
            List<Action> actions = new List<Action>();
            foreach (ActionDefinition aDef in this.mGraph.Domain.ActionDefinitions)
            {
                List<Action> tList = this.CreateActions(aDef);
                if (tList != null)
                    actions.AddRange(tList);
            }
            // all actions;
            this.mNumberActionsAdded = 0;

            List<Action> reducedActions = new List<Action>();
            foreach (Action a in actions)
            {
                //System.Console.WriteLine(a);
                if (!this.mSeenActions.ContainsKey(a))
                {
                    this.mSeenActions.Add(a, a);
                    reducedActions.Add(a);
                    this.mNumberActionsAdded++;
                }
            }

            this.InvokeActions(reducedActions);
            this.mActions.AddRange(reducedActions);
        }
        private List<Action> CreateActions(ActionDefinition aDef)
        {
            List<List<ValuedAtomicFormula>> total = new List<List<ValuedAtomicFormula>>();
            foreach(AtomicFormulaDefinition pre in aDef.Preconditions.OrderByDescending(af => af.Parameters.Count))
            {
                IEnumerable<ValuedAtomicFormula> result =
                    from x in (from vaf in this.mStartLits.Values
                    where vaf.AtomicFormula.Definition == pre.Predicate
                    && vaf.Value == true
                    && pre.Parameters.All(p => p.TypeMatches(vaf.AtomicFormula.Arguments[pre.Parameters.IndexOf(p)]))
                    select vaf)
                        where x != null
                        select x;
                total.Add(result.ToList<ValuedAtomicFormula>());
            }
            return this.CreateActions(aDef, total);
        }
        private List<Action> CreateActions(ActionDefinition def, List<List<ValuedAtomicFormula>> possiblePreconditions)
        {
            Dictionary<Parameter, PddlObject> map = new Dictionary<Parameter, PddlObject>();
            foreach (Parameter parm in def.Parameters)
                map.Add(parm, null);
            List<ValuedAtomicFormula> actualPreconditions = new List<ValuedAtomicFormula>(def.Preconditions.Count);
            for (int i = 0; i < def.Preconditions.Count; i++)
                actualPreconditions.Add(null);
            return this.CreateActionsRecursive(map, def, possiblePreconditions, actualPreconditions, 0);
        }
        private List<Action> CreateActionsRecursive(Dictionary<Parameter, PddlObject> map, 
                                                    ActionDefinition def, List<List<ValuedAtomicFormula>> possiblePreconditions, 
                                                    List<ValuedAtomicFormula> actualPreconditions, int i)
        {
            List<Action> returnList = new List<Action>();
            if (i == def.Preconditions.Count)
            {   
                // instantiate the action.
                List<PddlObject> args = new List<PddlObject>(def.Parameters.Count);
                foreach (Parameter key in def.Parameters)
                {
                    args.Insert(def.Parameters.IndexOf(key), map[key]);
                }

                IEnumerable<Parameter> res = from key in map.Keys
                                             where map[key] == null
                                             select key;
                if (res.Count() > 0)
                {
                    List<List<PddlObject>> posArgs = new List<List<PddlObject>>(res.Count());
                    List<Parameter> parms = new List<Parameter>(res.Count());
                    int index = 0;
                    foreach (Parameter key in res)
                    {
                        posArgs.Add(new List<PddlObject>());
                        parms.Add(key);
                        // get objects by type
                        posArgs[index].AddRange(this.mGraph.Problem.GetObjects(key));
                        index++;
                    }
                    // instantiate
                    List<Action> tList = this.CreateActionsRecursive2(def, args, posArgs, parms, 0);
                    if (tList != null)
                        returnList.AddRange(tList);
                }
                else
                {
                    Action a = new Action(def, args.ToList<PddlObject>());
                    if (!this.mSeenActions.ContainsKey(a))
                    {
                        returnList.Add(a);
                    }
                }
            }
            else
            {
                AtomicFormulaDefinition pre = def.Preconditions.OrderByDescending(af => af.Parameters.Count).ToArray()[i];
                List<ValuedAtomicFormula> vafList = possiblePreconditions[i];
                IEnumerable<ValuedAtomicFormula> result = null;
                if (i != 0)
                {
                    if (pre.Parameters.All(p => map[p] != null))
                    {
                        result = from vaf in vafList
                                 where vaf.AtomicFormula.Arguments.All(o => map.ContainsValue(o))
                                 select vaf;
                    }
                    else if (pre.Parameters.Any(p => map[p] != null))
                    {
                        result = from vaf in vafList
                                 where vaf.AtomicFormula.Arguments.Any(o => map.ContainsValue(o))
                                 select vaf;
                    }
                    else
                        result = (IEnumerable<ValuedAtomicFormula>)vafList;
                }
                else
                    result = (IEnumerable<ValuedAtomicFormula>)vafList;
                foreach(ValuedAtomicFormula vaf in result)
                {
                    for (int k = i; k < actualPreconditions.Count; k++)
                    {
                        actualPreconditions[k] = null;
                    }
                    if (!actualPreconditions.Contains(vaf))
                    {
                        actualPreconditions[i] = vaf;
                        List<Action> tList = new List<Action>();
                        List<Parameter> uParms = new List<Parameter>();
                        if (this.PopulateParameters(pre, vaf, map, uParms))
                        {
                            tList = this.CreateActionsRecursive(map, def, possiblePreconditions, actualPreconditions, i + 1);
                            if (tList != null)
                                returnList.AddRange(tList);
                        }
                        foreach (Parameter parm in uParms)
                            map[parm] = null;
                        
                    }
                }
            }
            return returnList;
        }
        private List<Action> CreateActionsRecursive2(ActionDefinition def, List<PddlObject> args, List<List<PddlObject>> posArgs, List<Parameter> parms, int i)
        {
            List<Action> returnList = new List<Action>();
            if (i == posArgs.Count)
            {
                Action a = new Action(def, args.ToList<PddlObject>());
                if (!this.mSeenActions.ContainsKey(a))
                {
                    returnList.Add(a);
                }
            }
            else
            {
                int index = def.Parameters.IndexOf(parms[i]);
                List<PddlObject> objs = posArgs[i];
                foreach (PddlObject obj in objs)
                {
                    if(!args.Contains(obj))
                    {
                        PddlObject tmp = args[index];
                        args[index] = obj;
                        List<Action> tList = this.CreateActionsRecursive2(def, args, posArgs, parms, i + 1);
                        if (tList != null)
                            returnList.AddRange(tList);
                        args[index] = tmp;
                    }
                }
            }
            return returnList;
        }
        private bool PopulateParameters(AtomicFormulaDefinition pre, ValuedAtomicFormula vaf, Dictionary<Parameter, PddlObject> map,
                                        List<Parameter> updated)
        {
            bool ok = true;
            for (int i = 0; i < pre.Parameters.Count; i++)
            {
                if (map[pre.Parameters[i]] == null)
                {
                    if (map.ContainsValue(vaf.AtomicFormula.Arguments[i]))
                    {
                        ok = false;
                        break;
                    }
                    else
                    {
                        ok = true;
                        updated.Add(pre.Parameters[i]);
                        map[pre.Parameters[i]] = vaf.AtomicFormula.Arguments[i];
                    }
                }
                else if (map[pre.Parameters[i]] == vaf.AtomicFormula.Arguments[i])
                {
                    ok = true;
                }
                else
                {
                    ok = false;
                    break;
                }
            }
            return ok;
        }
        private void CopyTo(Dictionary<Parameter, PddlObject> source, Dictionary<Parameter, PddlObject> dest)
        {
            foreach (Parameter key in source.Keys)
            {
                if (!dest.ContainsKey(key))
                    dest.Add(key, source[key]);
                else
                    dest[key] = source[key];
            }
        }

        private void InvokeActions(List<Action> actions)
        {
            foreach (Action action in actions)
            {
                this.AddAtomicFormulas(action.AddEffects, true);
                this.AddAtomicFormulas(action.DeleteEffects, false);
            }
        }
        private void RecursiveAddFormulas(Level level, ReadOnlyCollection<AtomicFormula> formulas, bool value)
        {
            level.AddAtomicFormulas(formulas, value);
            level.AddAtomicFormulasToStart(formulas, value);
            if (level.mPreviousLevel != null)
                this.RecursiveAddFormulas(level.mPreviousLevel, formulas, value);
        }
        private void AddAtomicFormulasToStart(ReadOnlyCollection<AtomicFormula> formulas, bool value)
        {
            foreach (AtomicFormula af in formulas)
            {
                ValuedAtomicFormula vaf = new ValuedAtomicFormula(af, value);
                if (!this.mStartLits.ContainsKey(vaf))
                    this.mStartLits.Add(vaf, vaf);
            }
        }
        private void AddAtomicFormulas(ReadOnlyCollection<AtomicFormula> formulas, bool value)
        {
            foreach (AtomicFormula af in formulas)
            {
                ValuedAtomicFormula vaf = new ValuedAtomicFormula(af, value);
                if (!this.mEndLits.ContainsKey(vaf))
                    this.mEndLits.Add(vaf, vaf);
            }
        }
        public bool StateIsPresent(ReadOnlyCollection<ValuedAtomicFormula> state)
        {
            bool present = true;
            foreach (ValuedAtomicFormula vaf in state)
            {
                present &= this.mEndLits.ContainsKey(vaf);
            }
            return present;
        }
        public ReadOnlyCollection<ValuedAtomicFormula> PositiveStartLiterals
        {
            get
            {
                IEnumerable<ValuedAtomicFormula> result = from vaf in this.mStartLits.Values where vaf.Value == true select vaf;
                return new ReadOnlyCollection<ValuedAtomicFormula>(result.ToList<ValuedAtomicFormula>());
            }
        }
        public ReadOnlyCollection<ValuedAtomicFormula> NegativeStartLiterals
        {
            get
            {
                IEnumerable<ValuedAtomicFormula> result = from vaf in this.mStartLits.Values where vaf.Value == false select vaf;
                return new ReadOnlyCollection<ValuedAtomicFormula>(result.ToList<ValuedAtomicFormula>());
            }
        }
        public ReadOnlyCollection<ValuedAtomicFormula> PositiveEndLiterals
        {
            get
            {
                IEnumerable<ValuedAtomicFormula> result = from vaf in this.mEndLits.Values where vaf.Value == true select vaf;
                return new ReadOnlyCollection<ValuedAtomicFormula>(result.ToList<ValuedAtomicFormula>());
            }
        }
        public ReadOnlyCollection<ValuedAtomicFormula> NegativeEndLiterals
        {
            get
            {
                IEnumerable<ValuedAtomicFormula> result = from vaf in this.mEndLits.Values where vaf.Value == false select vaf;
                return new ReadOnlyCollection<ValuedAtomicFormula>(result.ToList<ValuedAtomicFormula>());
            }
        }
        public ReadOnlyCollection<Action> Actions
        {
            get
            {
                return new ReadOnlyCollection<Action>(this.mActions);
            }
        }
        public ReadOnlyCollection<Operator> Operators
        {
            get
            {
                return new ReadOnlyCollection<Operator>(this.mOperators);
            }
        }
    }
}
